/*  

Cafechess Library: Cover basic chess programming needs, like GUI, Engine integration, 
FEN and PGN parsing.

                 \\|//
                 (o o)
--------------ooO-(_)-Ooo----------------------------------
Copyright (C) 2009  By Gregory A. Prentice Snoqualmie, WA
                    www.cafechess.org
                    cafechess@comcast.net
                      Oooo
--------------oooO----(  )---------------------------------
              (  )    (_/
               \_)

Microsoft Public License (Ms-PL)
               
Revisions:

Author        Date    Description:

*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace Cafechess.Reflections
{

  /// <summary>
  /// Used so that the calling program may specify what key to use in the hash table.
  /// </summary>
  public interface IBindInterfaceEvents
  {
    void GetBindKey(object forObject, ref object key);
  }

 
  /// <summary>
  /// Used to bind objects by a specific interface.
  /// </summary>
  public class BindInterfaces
  {
    #region Fields

    /// <summary>
    /// Holds the key / object pairs of the loaded objects.
    /// </summary>
    Hashtable coInterfaces;
    /// <summary>
    /// Will contain the number of interfaces that were not bound due to 
    /// not implementing the InterfaceName.
    /// </summary>
    int coErrorCount;
    /// <summary>
    /// The type of the interface the object must implement in order
    /// to be loaded.
    /// </summary>
    Type coInterfaceType;
    
    bool coLateBind;

    #endregion Fields

    #region EventsDelegates

    public delegate void GetBindKey(object forObject, ref object key);
    public event GetBindKey EventGetBindKey;

    #endregion EventsDelegates

    #region Properties
    public Hashtable Interfaces
    {
      get{ return coInterfaces; }
    }
    public bool LateBind
    {
      get { return coLateBind; }
      set { coLateBind = value; }
    }
    public int ErrorCount
    {
      get{return coErrorCount;}
    }
    public Type InterfaceType
    {
      get{ return coInterfaceType;}
      set{ coInterfaceType = value;}
    }
    #endregion Properties

    #region Methods

    /// <summary>
    /// Used to register event handlers.
    /// </summary>
    /// <param name="ievents"></param>
    public void AddEvents(IBindInterfaceEvents ievents)
    {
      EventGetBindKey += new GetBindKey(ievents.GetBindKey);
    }
    /// <summary>
    /// Used to remove event handlers.
    /// </summary>
    /// <param name="ievents"></param>
    public void RemoveEvents(IBindInterfaceEvents ievents)
    {
      EventGetBindKey -= new GetBindKey(ievents.GetBindKey);
    }

    /// <summary>
    /// Used to instantiate our interface binder class.
    /// </summary>
    /// <param name="InterfaceType">
    /// The full name of the interface an object must implement
    /// in order to be instantiated and added to our list.
    /// </param>
    public BindInterfaces(Type InterfaceType)
    {
      coInterfaceType = InterfaceType;
      coInterfaces = new Hashtable();
    }

    /// <summary>
    /// Returns true if we have successfully bound to at least one interfaces.
    /// </summary>
    /// <returns></returns>
    public bool HasBinded()
    {
      return (coInterfaces.Count > 0);
    }

    /// <summary>
    /// Performs the binding action for the specified interface named 
    /// by the string InterfaceName
    /// </summary>
    /// 
    /// <param name="oTypesToCreate">
    /// A collection of strings containing the fully qualified names of the objects to bind.
    /// Optional comma separated path to an Assembly the object lives in:
    ///  1) Norvanco.Router.EventProcessor; Norvanco.Router.TriggerProcessor
    ///  2) MyObjects.One,c:\myobjects\myobjects.dll;Norvanco.Router.TriggerProcessor
    /// </param>
    /// 
    /// <param name="rgoTypeKeys">
    /// The keys to use for indexing bound objects.  If the array is empty, or has fewer elements
    /// than oTypesToCreate, then the key used is the full name of the type.
    /// </param>
    /// 
    /// <param name="rgoConstructorParams">
    /// An array containing arrays of constructor parameters for each object to instantiate.
    /// rgoConstructorParams must match the length of oTypesToCreate.  For each indexed array,
    /// the arguments must match in number, order, and type the parameters of the constructor to invoke.
    /// If the indexed array is empty or a null reference, the constructor that takes no parameters
    /// is invoked.
    /// </param>
    public void Bind( IList oTypesToCreate, IList oTypeKeys, object[][] rgoConstructorParams, bool lateBind )
    { 
      // Clear each time this is called as we may be adding modules to our hash
      // at different times.  So just keep track of how many errors have occurred
      // during this bind.
      coErrorCount=0;
      LateBind = lateBind;
      for( int nTypePos = 0; nTypePos < oTypesToCreate.Count; ++nTypePos )
      {
        const byte PROCESS=0;
        const byte ASSEMBLY=1;
      
        string[] parms = ((string)oTypesToCreate[nTypePos]).Split( new char[] {','} );
        Assembly oAssembly = Assembly.GetCallingAssembly();

        // Are we loading a type from the calling Assembly?
        if( parms.Length > 1 )
        {
          // No, an assembly is specified in the type string.
          oAssembly = Assembly.LoadFrom( parms[ASSEMBLY] );
        }

        Type oTypeToCreate = oAssembly.GetType( parms[PROCESS] );

        // Make sure the type has implemented the specified interface.
        // If it does not then we will not create it.
        if (oTypeToCreate != null && oTypeToCreate.GetInterface(coInterfaceType.FullName) != null)
        {
          object anObject = null;
          // Get type key.
          object key = null;

          if (LateBind == false)
          {
            // Create the object from the given assembly.
            if (rgoConstructorParams == null || rgoConstructorParams.Length == 0)
            {
              anObject = Activator.CreateInstance(oTypeToCreate);
            }
            else
            {
              anObject = Activator.CreateInstance(oTypeToCreate, rgoConstructorParams[nTypePos]);
            }

            if (oTypeKeys != null && nTypePos < oTypeKeys.Count)
            {
              key = oTypeKeys[nTypePos];
            }
            else
            {
              key = oTypeToCreate.FullName;
            }
          }
          else
          {
            // Adding only type information.
            anObject = oTypeToCreate;
            key = oTypeToCreate.FullName;
          }

          // If any events are register, call and possibly change the key.
          if( EventGetBindKey != null )
            EventGetBindKey(anObject, ref key);

          // Add the object to our hash.
          coInterfaces[key] = anObject;
        }
        else
        {
          // count the number of objects that weren't loaded.
          ++coErrorCount;
        }
      }
    }


    /// <summary>
    /// Delegates the call to Bind( IList , IList , object[][] )
    /// </summary>
    /// <param name="oTypesToCreate"></param>
    public void Bind(IList oTypesToCreate)
    {
      Bind(oTypesToCreate, null, null, false);
    }

    /// <summary>
    /// Delegates the call to Bind( IList , IList , object[][] )
    /// </summary>
    /// <param name="oTypesToCreate"></param>
    public void Bind( IList oTypesToCreate, bool lateBind )
    {
      Bind( oTypesToCreate, null, null, lateBind );
    }

    /// <summary>
    /// Delegates the call to Bind( IList , IList , object[][] )
    /// </summary>
    /// <param name="oTypesToCreate"></param>
    /// <param name="oTypeKeys"></param>
    public void Bind( IList oTypesToCreate, IList oTypeKeys )
    {
      Bind( oTypesToCreate, oTypeKeys, null, false );
    }

    /// <summary>
    /// Delegates the call to Bind( IList , IList , object[][] )
    /// </summary>
    /// <param name="typesToCreate"></param>
    /// <param name="rgoConstructorParams"></param>
    public void Bind( IList typesToCreate, object[][] rgoConstructorParams )
    {
      Bind( typesToCreate, null, rgoConstructorParams, false );
    }

    public object CreateInstance(string key)
    {
      object anObject=null;
      // Add the object to our hash.
      Type oTypeToCreate = coInterfaces[key] as Type;
      if(oTypeToCreate != null )
        anObject = Activator.CreateInstance(oTypeToCreate);
      return anObject;
    }
    #endregion Methods
  }
}
